/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		sdmerge.cpp
 * PURPOSE:		security descriptor merge operations for a container type object
 *				used to implement auto-inheritance and replacement
 */

namespace SdMerge
{

struct MergeFilter
{
	virtual void FilterSid(SID_AND_STATE &Sid)
	{}

	virtual void FilterAcl(ACL_AND_STATE &Acl)
	{}

	//return reference to newly pushed ACE
	virtual void AppendAce(ACL &Acl, auto_buffer &Ab, ACE const &Src, SDE_ACE_HEADER const &NewAceHead)
	{
		ACE &Ace = *Ab.append_szpod(&Src);
		Acl.AceCount++;
		//Acl.AclSize = narrowing_cast<USHORT>(Ab.get_ct());
		Acl.AclSize += Src.Size;

		Ace.Revision = NewAceHead.Revision;
		Ace.Type = NewAceHead.Type;
		Ace.Flags = NewAceHead.Flags;
		Ace.Source = NewAceHead.Source;
		Ace.Mask = NewAceHead.Mask;
	}
}InertMergeFilter;

void AppendParentAce(ACL &Head, auto_buffer &Ab, BOOLEAN ChildIsContainer, ACE const &Src, MergeFilter *Filter)
{
	if(Src.Flags & (ChildIsContainer ? ACE_FLAG_CONTAINER_INHERIT | ACE_FLAG_OBJECT_INHERIT : ACE_FLAG_OBJECT_INHERIT))
	{
		SDE_ACE_HEADER DirectAce = *c_cast<SDE_ACE_HEADER *>(&Src);

		if(DirectAce.Flags & ACE_FLAG_INHERITED) //was the ACE previously inherited?
		{
			if(DirectAce.Flags & ACE_FLAG_INH_SOURCE_KNOWN)
				DirectAce.Source++;
			else
			{
				DirectAce.Source = 1;
				DirectAce.Flags |= ACE_FLAG_INH_SOURCE_KNOWN | ACE_FLAG_INH_SOURCE_MINONLY;
			}
		}
		else
		{
			DirectAce.Source = 1;
			DirectAce.Flags |= ACE_FLAG_INH_SOURCE_KNOWN;
		}

		if(!ChildIsContainer)
			DirectAce.Flags &= ~(ACE_FLAG_CONTAINER_INHERIT | ACE_FLAG_OBJECT_INHERIT); //nowhere to inherit to
		else
			if(Src.Flags & ACE_FLAG_NO_PROPAGATE)
				DirectAce.Flags &= ~(ACE_FLAG_CONTAINER_INHERIT | ACE_FLAG_OBJECT_INHERIT);
			else
				if(Src.Mask & (GENERIC_ALL | GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE))
				{
					DirectAce.Flags &= ~(ACE_FLAG_CONTAINER_INHERIT | ACE_FLAG_OBJECT_INHERIT); //instead, the IndirectAce will be inherited

					SDE_ACE_HEADER IndirectAce = *c_cast<SDE_ACE_HEADER *>(&Src);

					IndirectAce.Source = DirectAce.Source;

					IndirectAce.Flags |= (ACE_FLAG_INHERITED | ACE_FLAG_INHERIT_ONLY | ACE_FLAG_INH_SOURCE_KNOWN);

					Filter->AppendAce(Head, Ab, Src, IndirectAce);
				}

		DirectAce.Flags |= ACE_FLAG_INHERITED; //in any case, it's inherited now

		DirectAce.Flags &= ~(ACE_FLAG_NO_PROPAGATE | ACE_FLAG_INHERIT_ONLY); //these flags don't propogate

		Filter->AppendAce(Head, Ab, Src, DirectAce);
	}
}

void AppendChildAce(ACL &Head, auto_buffer &Ab, ACE &Src, MergeFilter *Filter)
{
	if(!(Src.Flags & ACE_FLAG_INHERITED)) //don't copy old inherited ACEs
		Filter->AppendAce(Head, Ab, Src, *c_cast<SDE_ACE_HEADER *>(&Src));
}

BOOLEAN IsDenyAce(UCHAR Type)
{
	switch(Type)
	{
		case ACCESS_DENIED_ACE_TYPE:
		case ACCESS_DENIED_OBJECT_ACE_TYPE:
		case ACCESS_DENIED_CALLBACK_ACE_TYPE:
		case ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE:
			return TRUE;

		default:
			return FALSE;
	}
}

//Db being destination buffer and Dh destination header
void AppendAcl(ACL &Dh, auto_buffer &Db, ACL_AND_STATE const &Src, BOOLEAN ChildIsContainer, BOOLEAN ParentSrc, BOOLEAN AppendDeny, BOOLEAN AppendNonDeny, MergeFilter *Filter)
{
	if(Src.State & ACL_FLAG_PRESENT && Src.Acl)
	{
		ptr_buffer<ACE> Sb(c_cast<ACE *>(Src.Acl.get())); //source buffer
		Sb.pos += sizeof(ACL);

		for(USHORT i = 0; i < Src.Acl->AceCount; i++)
		{
			if(IsDenyAce(Sb.data->Type) ? AppendDeny : AppendNonDeny)
				if(ParentSrc)
					AppendParentAce(Dh, Db, ChildIsContainer, *Sb.data, Filter);
				else
					AppendChildAce(Dh, Db, *Sb.data, Filter);
			Sb.pos += Sb.data->Size;
		}
	}
}

void MergeAclState(ACL_AND_STATE const &Parent, ACL_AND_STATE &Child, BOOLEAN ChildIsContainer, BOOLEAN MergeAllObjects, GENERIC_MAPPING Gm, MergeFilter *Filter)
{
	if(!(Parent.State & ACL_FLAG_PRESENT)) //noop without parent
		return;

	if(!MergeAllObjects && !(Parent.State & ACL_FLAG_CHANGED)) //if parent not changed and not merging all, nothing to do
		return;

	if(!Parent.Acl && !Child.Acl) //null acl->null acl
		return;

	if((Parent.State & ACL_MERGE_MODE_BITS) == ACL_MERGE_MODE_NONE) //don't do anything
		return;

	ACL_AND_STATE AclAndState;
	ULONG MergeMode;
	auto_buffer Ab; //buffer for accumulating new ACL data
	ACL *Acl;

	if(!Parent.Acl) //there's no parent, but there is a child acl
	{
		MergeMode = ACL_MERGE_MODE_INH_NONE;
		Ab.increase_maxct(Child.Acl->AclSize);
		Acl = Ab.append<ACL>(*Child.Acl);
	}
	else if(!Child.Acl) //there no child, but there is a parent
	{
		MergeMode = ACL_MERGE_MODE_INH_ONLY;
		Ab.increase_maxct(Parent.Acl->AclSize * 2); //each parent ace could become up to 2 (each the size of the original)
		Acl = Ab.append<ACL>(*Parent.Acl);
	}
	else //else, merge as requested
	{
		MergeMode = Parent.State & ACL_MERGE_MODE_BITS;
		Ab.increase_maxct(Parent.Acl->AclSize * 2 + Child.Acl->AclSize); //each parent ace could become up to 2 (each the size of the original), and each child persist
		Acl = Ab.append<ACL>(*Child.Acl);
	}

	AclAndState.State = Child.State;
	
	if(Parent.State & ACL_FLAG_MERGE_UNPROTECT)
		AclAndState.State &= ~ACL_FLAG_PROTECTED;

	if(AclAndState.State & ACL_FLAG_PROTECTED)
		return;

	Acl->AclRevision = max(Parent.Acl ? Parent.Acl->AclRevision : 0, Child.Acl ? Child.Acl->AclRevision : 0);
	Acl->AclSize = sizeof(ACL);
	Acl->AceCount = 0;

	switch(MergeMode)
	{
	case ACL_MERGE_MODE_INH_NONE:
		AppendAcl(*Acl, Ab, Child, ChildIsContainer, FALSE, TRUE, TRUE, Filter); //changed if any previously inherited ACEs were dropped
		break;

	case ACL_MERGE_MODE_INH_ONLY:
		AppendAcl(*Acl, Ab, Parent, ChildIsContainer, TRUE, TRUE, TRUE, Filter);
		break;

	case ACL_MERGE_MODE_INH_LAST:
		AppendAcl(*Acl, Ab, Child, ChildIsContainer, FALSE, TRUE, TRUE, Filter);
		AppendAcl(*Acl, Ab, Parent, ChildIsContainer, TRUE, TRUE, TRUE, Filter);
		break;

	case ACL_MERGE_MODE_INH_FIRST:
		AppendAcl(*Acl, Ab, Parent, ChildIsContainer, TRUE, TRUE, TRUE, Filter);
		AppendAcl(*Acl, Ab, Child, ChildIsContainer, FALSE, TRUE, TRUE, Filter);
		break;

	case ACL_MERGE_MODE_DENY_FIRST:
		AppendAcl(*Acl, Ab, Child, ChildIsContainer, FALSE, TRUE, FALSE, Filter);
		AppendAcl(*Acl, Ab, Parent, ChildIsContainer, TRUE, TRUE, FALSE, Filter);
		AppendAcl(*Acl, Ab, Child, ChildIsContainer, FALSE, FALSE, TRUE, Filter);
		AppendAcl(*Acl, Ab, Parent, ChildIsContainer, TRUE, FALSE, TRUE, Filter);
		break;

	default:
		RaiseTextError(L"Invalid merge mode");
	}

	//did the ACL change compared to its previous state?
	if(!AreAclsEffectivelyEqual(Acl, Child.Acl, Gm))
	{
		AclAndState.Acl = Ab.release_as<ACL>();

		AclAndState.State |= ACL_FLAG_PRESENT | ACL_FLAG_AUTO_INHERIT | ACL_FLAG_CHANGED |
			(Parent.State & (ACL_MERGE_MODE_BITS | ACL_FLAG_MERGE_UNPROTECT));

		Child = AclAndState;
	}

	Filter->FilterAcl(Child);
}

void MergeSidState(SID_AND_STATE const &Parent, SID_AND_STATE &Child, MergeFilter *Filter)
{
	switch(Parent.State & SID_MERGE_MODE_BITS)
	{
	case SID_MERGE_MODE_NONE:
		break;

	case SID_MERGE_MODE_REPLACE:
		Child.Sid = Parent.Sid;
		Child.State = Parent.State | SID_FLAG_CHANGED;
		break;
	}

	Filter->FilterSid(Child);
}

void MergeEditState(SD_EDIT_STATE const &Parent, SD_EDIT_STATE &Child, GENERIC_MAPPING Gm, MergeFilter *Filter OPTIONAL = &InertMergeFilter)
{
	BOOLEAN ChildIsContainer = (Child.State & SD_FLAG_CONTAINER) != 0;
	BOOLEAN MergeAllObjects = (Parent.State & SD_MERGE_ALL_OBJECTS) != 0;

	MergeAclState(Parent.Dacl, Child.Dacl, ChildIsContainer, MergeAllObjects, Gm, Filter);
	MergeAclState(Parent.Sacl, Child.Sacl, ChildIsContainer, MergeAllObjects, Gm, Filter);
	MergeSidState(Parent.Owner, Child.Owner, Filter);
	MergeSidState(Parent.Group, Child.Group, Filter);

	Child.State |= Parent.State & SD_MERGE_ALL_OBJECTS;
}

void MergeTreeEditState(SD_EDIT_STATE const &NewState, ObjectTypes::Object &Co, UCHAR Privileges, MergeFilter *Filter OPTIONAL = &InertMergeFilter)
{
	struct WalkerClass : ObjectTypes::ChildrenWalkDest
	{
		SECURITY_INFORMATION Si;
		SD_EDIT_STATE const *ParentState;
		UCHAR Privileges;
		MergeFilter *Filter;
		WalkerClass(SECURITY_INFORMATION Si, SD_EDIT_STATE const *ParentState, UCHAR Privileges, MergeFilter *Filter) :
			Si(Si),
			ParentState(ParentState),
			Privileges(Privileges),
			Filter(Filter)
		{}

		void Proc(ObjectTypes::Object &Co)
		{
			CppCancelOneHandlerExistance CCOHE;
			try
			{
				OpText Ot(L"Merging", Co.Name);

				SD_EDIT_STATE ChildState;
				Co.QueryEditState(Si, ChildState);

				MergeEditState(*ParentState, ChildState, Co.GetGenericMapping(), Filter);

				BOOL Changed = ModifiedSdEditStateSecInfo(ChildState) != 0;

				if(Changed)
					Co.SetEditState(ChildState);

				if((ChildState.State & SD_FLAG_CONTAINER) && (Changed || (ChildState.State & SD_MERGE_ALL_OBJECTS)))
				{
					SECURITY_INFORMATION ChildSi = ChildState.State & SD_MERGE_ALL_OBJECTS ? Si : ModifiedSdEditStateSecInfo(ChildState);

					WalkerClass Walker(ChildSi, &ChildState, Privileges, Filter);
					Co.WalkChildren(Walker, Privileges);
				}

				Ot;
			}
			catch(CancelOneException)
			{}
		}
	};

	if((NewState.State & SD_MERGE_ALL_OBJECTS) || ModifiedSdEditStateSecInfo(NewState))
	{
		Co.SetEditState(NewState);

		if(NewState.State & SD_FLAG_MERGE_FULL_CTRL)
		{
			WalkerClass Walker(ModifiedSdEditStateSecInfo(NewState), &NewState, Privileges, Filter);

			Co.WalkChildren(Walker, Privileges);
		}
	}
}

void SetFullSupportFlags(SD_EDIT_STATE &Es)
{
	Es.State |= SD_FLAG_MERGE_FULL_CTRL;
	Es.Dacl.State |= ACL_FLAG_MERGE_FULL_CTRL;
	Es.Sacl.State |= ACL_FLAG_MERGE_FULL_CTRL;
	Es.Owner.State |= SID_FLAG_MERGE_FULL_CTRL;
	Es.Group.State |= SID_FLAG_MERGE_FULL_CTRL;
}

}//namespace SdMerge